/*
 * Copyright (c) 2012. The Genome Analysis Centre, Norwich, UK
 * MISO project contacts: Robert Davey, Mario Caccamo @ TGAC
 * *********************************************************************
 *
 * This file is part of MISO.
 *
 * MISO is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MISO is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MISO.  If not, see <http://www.gnu.org/licenses/>.
 *
 * *********************************************************************
 */

package uk.ac.bbsrc.tgac.miso.core.plugin.annotation;

//import uk.ac.ebi.arrayexpress2.annotation.ArrayExpress2Metadata;
//import uk.ac.ebi.arrayexpress2.annotation.ArrayExpress2Operation;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

/**
 * uk.ac.bbsrc.tgac.miso.core.plugin.annotation
 *
 * Searches for MISO annotations and generates information about how
 * to invoke the tools.  This introspector can be used to examine jar files or
 * the classpath.
 * <p/>
 * This introspector can also be used to generate a client stub, which can then
 * be invoked directly from the command line using a client that binds arguments
 * to the generated stubs.
 *
 * @author Tony Burdett
 * @author Rob Davey
 * @date 20-Jul-2010
 * @since 0.0.2
 */
public class MisoPluginAnalyser {
  /**
   * Probes the set of currently loaded classes and uses the file
   * "META-INF/miso/plugins" to discover those annotated with {@link
   * MisoOperation} annotations. The
   * plugins file is a SPI-type file, that lists all the annotated
   * classes, and is generated by annotation processing at compile-time. Each
   * resource thus annotated is used to extract an {@link
   * MisoMetadata} object, and the set of all metadata returned.
   *
   * @return the metadata describing all MISO tools found
   * @throws java.io.IOException    if there was a problem reading from the
   *                                resource "META-INF/miso/plugins"
   * @throws ClassNotFoundException if a class described in one of the
   *                                META-INF/miso/plugins was not be
   *                                loaded
   */

//  public Collection<MisoMetadata> analyse()
//      throws IOException, ClassNotFoundException {
//    return analyseByLoader(getClass().getClassLoader());
//  }

  /**
   * Probes all classes in the supplied jar file and uses the file
   * "META-INF/miso/plugins" to discover those annotated with {@link
   * MisoOperation} annotations. The
   * plugins file is a SPI-type file, that lists all the annotated
   * classes, and is generated by annotation processing at compile-time. Each
   * resource thus annotated is used to extract an {@link
   * MisoMetadata} object, and the set of all metadata returned.
   *
   * @param pluginJarFile the jar file containing MISO tool(s) that we
   *                   want to analyse
   * @return the metadata describing all MISO tools found
   * @throws java.io.IOException    if there was a problem reading from the
   *                                resource "META-INF/miso/plugins"
   * @throws ClassNotFoundException if a class described in one of the
   *                                META-INF/miso/plugins was not be
   *                                loaded
   */

/*  public Collection<MisoMetadata> analyse(File pluginJarFile)
      throws IOException, ClassNotFoundException {
    // load the supplied jar file
    return analyseByLoader(new URLClassLoader(
        new URL[]{pluginJarFile.getAbsoluteFile().toURI().toURL()}));
  }

  private Collection<MisoMetadata> analyseByLoader(ClassLoader loader)
      throws IOException, ClassNotFoundException {
    // collection of metadata objects to return
    Set<MisoMetadata> metadata = new HashSet<MisoMetadata>();

    // grab services-type file: this is a list of classes annotated with @MisoPlugin
    Enumeration<URL> resources =
        loader.getResources("META-INF/miso/plugins");

    while (resources.hasMoreElements()) {
      URL resource = resources.nextElement();

      // read generator line
      BufferedReader reader =
          new BufferedReader(new InputStreamReader(resource.openStream()));

      String line;
      while ((line = reader.readLine()) != null) {
        Class m = Class.forName(line.trim());
        metadata.add(examineClass(m));
      }
    }

    return metadata;
  }

  private MisoMetadata examineClass(Class misoPluginClass) {
    MisoMetadataImpl misoMetadata = null;

    // reflect class to discover operation name
    if (misoPluginClass.isAnnotationPresent(MisoPlugin.class)) {
      misoMetadata = new MisoMetadataImpl(misoPluginClass);

      // reflect all methods on this class to discover operations
      for (Method m : misoPluginClass.getMethods()) {
        if (m.isAnnotationPresent(MisoOperation.class)) {
          examineMethods(misoMetadata, m);
        }
      }
    }

    return misoMetadata;
  }

  private void examineMethods(MisoMetadataImpl misoMetadata,
                              Method misoOperationMethod) {
    // create operation by checking annotations on the supplied method
    MisoOperation misoOp =
        misoOperationMethod.getAnnotation(MisoOperation.class);

    // read operation metadata
    final String opName = misoOp.name();
    final String opDescription = misoOp.description();
    final int parallelProcesses = misoOp.maxParallelProcesses();
    final int memRequirement = misoOp.maxMemoryRequirement();
    final Collection<MisoMetadata.Parameter> parameters =
        new HashSet<MisoMetadata.Parameter>();
    final String methodName = misoOperationMethod.getName();

    // read parameter metadata
    Annotation[][] parameterAnnotations =
        misoOperationMethod.getParameterAnnotations();
    for (int pos = 0; pos < parameterAnnotations.length; pos++) {
      // check next argument for MisoParameter annotations
      for (Annotation annotation : parameterAnnotations[pos]) {
        if (annotation instanceof MisoParameter) {
          // found annotation, create argument
          MisoParameter misoP = (MisoParameter) annotation;

          final String paramName = misoP.name();
          final String paramDescription = misoP.description();
          final int paramPosition = pos;

          MisoMetadata.Parameter parameter =
              new MisoMetadata.Parameter() {
                @Override
                public String getParameterName() {
                  return paramName;
                }

                @Override
                public String getParameterDescription() {
                  return paramDescription;
                }

                @Override
                public int getParameterPosition() {
                  return paramPosition;
                }
              };

          parameters.add(parameter);
        }
      }
    }

    // create the operation
    MisoMetadata.Operation operation =
        new MisoMetadata.Operation() {
          @Override
          public String getOperationName() {
            return opName;
          }

          @Override
          public String getOperationDescription() {
            return opDescription;
          }

          @Override
          public int getMaxAllowedParallelProcesses() {
            return parallelProcesses;
          }

          @Override
          public int getMaxMemoryRequirement() {
            return memRequirement;
          }

          @Override
          public Collection<MisoMetadata.Parameter> getParameters() {
            return parameters;
          }

          @Override public String getMethodName() {
            return methodName;
          }
        };

    misoMetadata.addOperation(operation);
  }

  private class MisoMetadataImpl implements MisoMetadata {
    private Class misoPluginClass;

    private String name;
    private String description;
    private Set<Operation> operations;

    public MisoMetadataImpl(Class mpc) {
      this.misoPluginClass = mpc;

      // examine annotations, extract mode name
      Annotation ann = mpc.getAnnotation(
          MisoPlugin.class);
      if (ann instanceof MisoPlugin) {
        MisoPlugin mp = (MisoPlugin) ann;

        this.name = mp.name();
        this.description = mp.description();

        this.operations = new HashSet<Operation>();
      }
    }

    @Override
    public String getToolName() {
      return name;
    }

    @Override public String getToolDescription() {
      return description;
    }

    @Override
    public Collection<Operation> getOperations() {
      return operations;
    }

    @Override public String getClassName() {
      return misoPluginClass.getName();
    }

    public void addOperation(Operation operation) {
      operations.add(operation);
    }
  }
  */
}
